﻿//-----------------------------------------------------------------------
// <copyright file="ConsoleHostViewModel.cs" author="Artak Mkrtchyan">
//     Creative Commons - GNU General Public License (http://creativecommons.org/licenses/GPL/2.0/
// </copyright>
// <author>Artak Mkrtchyan</author>
// <email>mkArtak@gmail.com</email>
// <date>15/07/2012</date>
//-----------------------------------------------------------------------

using ConsoleHoster.Common.Model;
using ConsoleHoster.Common.Notifications;
using ConsoleHoster.Common.Plugins;
using ConsoleHoster.Common.UserInput;
using ConsoleHoster.Common.Utilities;
using ConsoleHoster.Common.Utilities.IO;
using ConsoleHoster.Common.Utilities.Threading;
using ConsoleHoster.Common.ViewModel;
using ConsoleHoster.Model;
using ConsoleHoster.Model.Configuration;
using ConsoleHoster.Model.Entities;
using ConsoleHoster.Model.NativeWrappers;
using ConsoleHoster.Model.Plugins;
using ConsoleHoster.ViewModel.Entities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;

namespace ConsoleHoster.ViewModel
{
    public partial class ConsoleHostViewModel : ViewModelBase, IDisposable
    {
        internal const string NewProjectName = "New Project";
        internal const string DefaultExecutableName = "cmd.exe";

        internal const string ProjectInstanceNameFormat = "{0}_{1}";
        internal const string DuplicateProjectNameFormat = "Duplicate {0}_{1}";
        internal const string RemoveProjectStatus = "Removing Project...";

        private readonly Object projectsLock = new Object();

        private readonly Lazy<ICommand> quickConsoleCommand;
        private readonly Lazy<ICommand> loadProjectCommand;
        private readonly Lazy<ICommand> customConsoleCommand;

        private readonly IStorageManager storageManager;
        private readonly IKeyboardStateProvider keyboardStateProvider;
        private readonly IProcessManager processManager;
        private readonly IUserInteractionManager userInteractionManager;
        private readonly IConfigurationProvider configurationProvider;
        private readonly IFontSettingsProvider fontSettingsProvider;
        private readonly IGlobalCommandsProvider globalCommandsProvider;
        private readonly IProjectsManager projectsManager;
        private readonly IFileSystemContentProvider fileSystemContentProvider;
        private readonly ISaveDestinationProvider saveDestinationProvider;
        private readonly IFileProvider fileProvider;
        private readonly IProjectsFilter projectsFilter;
        private readonly IImportLinkProvider importLinkProvider;
        private readonly IKeyboardInputHandler keyboardInputHandler;
        private readonly IPluginManager pluginManager;

        private readonly Func<IWindowsShortcut> shortcutFactory;
        private readonly Func<string, ILogger> outputLoggerFactory;

        /// <summary>
        /// The "projects" collection stores the started project instances
        /// </summary>
        private ObservableCollection<IConsoleVM> projectsInstances = new ObservableCollection<IConsoleVM>();
        private ConsoleViewModel activeConsole = null;
        private ObservableCollection<ConsoleProjectViewModel> availableProjects = new ObservableCollection<ConsoleProjectViewModel>();
        private IList<CommandDataViewModel> globalCommands = new List<CommandDataViewModel>();
        private FontSettings currentFontSettings;
        private bool? isAdminRole = null;
        private IList<IPlugin> plugins = new List<IPlugin>();

        private readonly Object loadingLock = new Object();
        private bool isLoaded = false;

        #region Constructors
        public ConsoleHostViewModel(
            IDispatcher argDispatcher,
            ViewSettings argViewSettings,
            ILogger argLogger)
            : this(
                argDispatcher,
                argViewSettings,
                new ProcessManager(),
                new KeyboardStateProvider(),
                new FileSystemContentProvider(),
                new XmlStorage(),
                argLogger)
        {
        }

        private ConsoleHostViewModel(
            IDispatcher argDispatcher,
            ViewSettings argViewSettings,
            IProcessManager argProcessManager,
            IKeyboardStateProvider argKeyboardStateProvider,
            IFileSystemContentProvider argFileSystemContentProvider,
            IXmlStorage argXmlStorage,
            ILogger argLogger)
            : this(
                argDispatcher,
                argViewSettings,
                argProcessManager,
                argKeyboardStateProvider,
                argFileSystemContentProvider,
                new StorageManager(argFileSystemContentProvider, argXmlStorage, argLogger),
                argLogger)
        {
        }

        private ConsoleHostViewModel(
            IDispatcher argDispatcher,
            ViewSettings argViewSettings,
            IProcessManager argProcessManager,
            IKeyboardStateProvider argKeyboardStateProvider,
            IFileSystemContentProvider argFileSystemContentProvider,
            IStorageManager argStorageManager,
            ILogger argLogger)
            : this(
                new DefaultConfigurationProvider(key => ConfigurationManager.AppSettings[key], argLogger),
                new KeyboardInputHandler(argKeyboardStateProvider, argLogger),
                new UserInteractionManager(argDispatcher, argViewSettings, argLogger),
                new UpdateManager("ConsoleHoster", argProcessManager, argLogger),
                argKeyboardStateProvider,
                argProcessManager,
                new DefaultFontSettingsProvider(argDispatcher, argViewSettings, argLogger),
                new DefaultGlobalCommandsProvider(argDispatcher, argViewSettings, argLogger),
                new DefaultProjectsManager(argDispatcher, argViewSettings, argLogger),
                new DefaultLinkProvider(argLogger),
                argStorageManager,
                new PluginManager(argLogger, argStorageManager),
                argFileSystemContentProvider,
                new SaveDestinationProvider(),
                new DialogBasedFileProvider(),
                new ProjectsWithCommandFilter(argDispatcher, argLogger, argViewSettings),
                argDispatcher,
                argLogger,
                () => new WindowsShortcut(),
                path => new SimpleFileLogger(path))
        {
        }

        internal ConsoleHostViewModel(
            IConfigurationProvider argConfigurationProvider,
            IKeyboardInputHandler argKeyboardInputHandler,
            IUserInteractionManager argUserInputRequester,
            IUpdateManager argUpdateManager,
            IKeyboardStateProvider argKeyboarStateProvider,
            IProcessManager argProcessManager,
            IFontSettingsProvider argFontSettingsProvider,
            IGlobalCommandsProvider argGlobalCommandsProvider,
            IProjectsManager argProjectsManager,
            IImportLinkProvider argImportLinkProvider,
            IStorageManager argStorageManager,
            IPluginManager argPluginManager,
            IFileSystemContentProvider argFileSystemContentProvider,
            ISaveDestinationProvider argSaveDestinationProvider,
            IFileProvider argFileProvider,
            IProjectsFilter argProjectsFilter,
            IDispatcher argDispatcher,
            ILogger argLogger,
            Func<IWindowsShortcut> argShortcutFactory,
            Func<string, ILogger> argOutputLoggerFactory)
            : base(argDispatcher, argLogger)
        {
            ValidationManager.RequireArgumentNotNull(argKeyboarStateProvider, "argKeyboarStateProvider");
            ValidationManager.RequireArgumentNotNull(argProcessManager, "argProcessManager");
            ValidationManager.RequireArgumentNotNull(argUserInputRequester, "argUserInputRequester");
            ValidationManager.RequireArgumentNotNull(argConfigurationProvider, "argConfigurationProvider");
            ValidationManager.RequireArgumentNotNull(argFontSettingsProvider, "argFontSettingsProvider");
            ValidationManager.RequireArgumentNotNull(argGlobalCommandsProvider, "argGlobalCommandsProvider");
            ValidationManager.RequireArgumentNotNull(argStorageManager, "argStorageManager");
            ValidationManager.RequireArgumentNotNull(argProjectsManager, "argProjectsManager");
            ValidationManager.RequireArgumentNotNull(argImportLinkProvider, "argImportLinkProvider");
            ValidationManager.RequireArgumentNotNull(argFileSystemContentProvider, "argFileSystemContentProvider");
            ValidationManager.RequireArgumentNotNull(argPluginManager, "argPluginManager");
            ValidationManager.RequireArgumentNotNull(argSaveDestinationProvider, "argSaveDestinationProvider");
            ValidationManager.RequireArgumentNotNull(argFileProvider, "argFileProvider");
            ValidationManager.RequireArgumentNotNull(argShortcutFactory, "argShortcutFactory");
            ValidationManager.RequireArgumentNotNull(argProjectsFilter, "argProjectsFilter");
            ValidationManager.RequireArgumentNotNull(argOutputLoggerFactory, "argOutputLoggerFactory");

            this.fontSettingsProvider = argFontSettingsProvider;
            this.globalCommandsProvider = argGlobalCommandsProvider;
            this.projectsManager = argProjectsManager;
            this.importLinkProvider = argImportLinkProvider;
            this.configurationProvider = argConfigurationProvider;
            this.pluginManager = argPluginManager;
            this.userInteractionManager = argUserInputRequester;
            this.updateManager = argUpdateManager;
            this.processManager = argProcessManager;
            this.storageManager = argStorageManager;
            this.fileSystemContentProvider = argFileSystemContentProvider;
            this.saveDestinationProvider = argSaveDestinationProvider;
            this.fileProvider = argFileProvider;

            this.shortcutFactory = argShortcutFactory;
            this.outputLoggerFactory = argOutputLoggerFactory;

            this.PropertyChanged += OnNotifyPropertyChanged;
            this.keyboardStateProvider = argKeyboarStateProvider;
            this.keyboardInputHandler = argKeyboardInputHandler;
            this.projectsFilter = argProjectsFilter;

            this.quickConsoleCommand = new Lazy<ICommand>(() => new RelayCommand(p => this.CreateQuickConsoleAsync()));
            this.loadProjectCommand = new Lazy<ICommand>(() => new RelayCommand(p => this.StartProjectAsync(p as ConsoleProjectViewModel)));
            this.customConsoleCommand = new Lazy<ICommand>(() => new RelayCommand(p => this.RunCommand(p as CommandDataViewModel)));
        }
        #endregion

        public void Dispose()
        {
            this.RunActionSwitchingToBusyState(() =>
                {
                    this.Logger.LogMessage("Disposing...");
                    while (this.Projects.Any())
                    {
                        this.Projects[0].Close();
                    }
                    this.Logger.LogMessage("Disposal finished successfully !");
                }, true, "Closing active projects...");
        }

        /// <summary>
        /// Loads the object asynchronousely, and returns true, if the load had happened.
        /// If the object was already loaded - returns false.
        /// </summary>
        /// <returns>true, if the method is called the first time</returns>
        public async Task<bool> LoadAsync()
        {
            bool tmpShouldLoad = false;

            if (!this.isLoaded)
            {
                lock (this.loadingLock)
                {
                    if (!this.isLoaded)
                    {
                        this.isLoaded = true;
                        tmpShouldLoad = true;
                    }
                }
            }

            if (tmpShouldLoad && !this.IsInDesignMode)
            {
                Task tmpStratupDataLoadingTask = this.LoadStartupDataAsync();
                Task tmpUpdateCheckTask = this.CheckForUpdatesAsync(true);

                await Task.WhenAll(tmpStratupDataLoadingTask, tmpUpdateCheckTask);
            }
            return tmpShouldLoad;
        }

        private Task LoadStartupDataAsync()
        {
            return this.RunAsyncAction(() =>
                {
                    this.Logger.LogMessage("Loading startup data..");

                    FontSettings tmpFontSettings;
                    IEnumerable<CommandData> tmpCommands;
                    this.storageManager.LoadGlobalSettings(out tmpFontSettings, out tmpCommands);
                    this.CurrentFontSettings = tmpFontSettings;

                    this.LoadProjects();
                    this.LoadGlobalCommands(tmpCommands);
                    this.LoadPlugins();
                    this.Logger.LogMessage("Finished loading startup data");
                }, true, "Loading", true);
        }

        public void RemoveActiveProject()
        {
            this.RemoveProject(this.ActiveConsole.Project.Name);
        }

        private async void RemoveProject(string argProjectName)
        {
            if (!this.userInteractionManager.Confirm("Are you sure you want to delete current project ?\nPlease note, that this will also close all the open instances of that project", "Delete Project"))
            {
                return;
            }

            ConsoleProjectViewModel tmpCurrentProject = null;
            this.RunActionSwitchingToBusyState(() =>
            {
                tmpCurrentProject = this.AvailableProjects.Where(item => item.Name.Equals(argProjectName, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();
                if (tmpCurrentProject == null)
                {
                    throw new ApplicationException("No such project");
                }

                this.AvailableProjects.Remove(tmpCurrentProject);

                this.CloseRunningInstancesOfProject(argProjectName);
            }, true, RemoveProjectStatus);

            await this.RunAsyncAction(() =>
                {
                    this.storageManager.DeleteProject(tmpCurrentProject.Name);
                }, true, RemoveProjectStatus, true);
        }

        private void CloseRunningInstancesOfProject(string argProjectName)
        {
            IEnumerable<IConsoleVM> tmpOpenProjects = this.GetRunningProjectInstances(argProjectName);
            foreach (IConsoleVM tmpConsole in tmpOpenProjects)
            {
                tmpConsole.Close();
            }
        }

        private IEnumerable<IConsoleVM> GetRunningProjectInstances(string argProjectName)
        {
            lock (this.projectsLock)
            {
                return this.Projects.Where(item => IsInstanceOfProjectType(item, argProjectName)).ToList();
            }
        }

        private static bool IsInstanceOfProjectType(IConsoleVM argConsoleVM, string argProjectName)
        {
            string tmpPattern = String.Format(ProjectInstanceNameFormat, "^" + argProjectName, @"\d+$");
            return Regex.IsMatch(argConsoleVM.Name, tmpPattern);
        }

        public async Task UpdateGlobalCommands()
        {
            IEnumerable<CommandData> tmpCommands = null;
            this.RunActionWithErrorHandling(() =>
                {
                    IEnumerable<CommandData> tmpGlobalCommands = new List<CommandData>();
                    if (this.GlobalCommands != null)
                    {
                        tmpGlobalCommands = this.GlobalCommands.Select(item => item.Model);
                    }

                    tmpCommands = this.GlobalCommandsProvider.ProvideData(tmpGlobalCommands);
                }, "Failed to update global commands");

            if (tmpCommands != null)
            {
                await this.RunActionOnUIThread(() =>
                    {
                        this.GlobalCommands = tmpCommands.Select(item => new CommandDataViewModel(item)).ToList();
                    }, true, System.Windows.Threading.DispatcherPriority.Background);

                await this.RunAsyncAction(async () => await this.storageManager.SaveGlobalCommands(tmpCommands), true, "Saving global commands", true);
            }
        }

        /// <summary>
        /// Switches from the current active console to the next or previous one
        /// </summary>
        /// <param name="argSwitchToNext">
        /// Determines the direction of the switch. If the value is true, the next console becomes active, otherwise - the previous one
        /// </param>
        public void SwitchToConsole(bool argSwitchToNext)
        {
            this.RunActionWithErrorHandling(() =>
                {
                    this.Logger.LogMessage("Switching to the {0} console", argSwitchToNext ? "next" : "previous");
                    int tmpNextProjectIndex;
                    lock (this.projectsLock)
                    {
                        tmpNextProjectIndex = this.Projects.IndexOf(this.ActiveConsole) + (argSwitchToNext ? 1 : -1);

                        if (tmpNextProjectIndex == this.Projects.Count)
                        {
                            tmpNextProjectIndex = 0;
                        }
                        else if (tmpNextProjectIndex < 0)
                        {
                            tmpNextProjectIndex = this.Projects.Count - 1;
                        }

                        this.ActiveConsole = this.Projects[tmpNextProjectIndex] as ConsoleViewModel;
                    }
                });
        }

        private IWindowsShortcut GetShortcutForProject(ConsoleProjectViewModel argProject)
        {
            IWindowsShortcut tmpResult = this.shortcutFactory();
            tmpResult.Arguments = argProject.Arguments;
            tmpResult.Target = argProject.Executable;
            tmpResult.WorkingDirectory = argProject.WorkingDir;
            return tmpResult;
        }

        public void ExportCurrentProjectShortcut()
        {
            this.RunActionWithErrorHandling(() =>
                {
                    this.Logger.LogMessage("Exporting project: '{0}' shurtcut.", this.ActiveConsole.Project.Name);

                    this.saveDestinationProvider.Filter = "*.lnk|Shortcut files";
                    string tmpResult = this.saveDestinationProvider.ProvideData(GetNameForExportingShortcut(this.ActiveConsole.Project.Name));
                    if (tmpResult != null)
                    {
                        this.ExportCurrentProjectShortcut(tmpResult);
                    }
                });
        }

        public void ImportProjects()
        {
            this.RunActionWithErrorHandling(() =>
                {
                    this.Logger.LogMessage("Starting importing projects...");

                    string tmpFile = this.fileProvider.ProvideData(null);
                    if (tmpFile != null)
                    {
                        IEnumerable<ConsoleProject> tmpLoadedProjects = this.storageManager.LoadProjects(tmpFile);
                        this.projectsFilter.Mode = ProjectsFilterMode.Import;
                        IEnumerable<ConsoleProject> tmpProjectsToImport = this.projectsFilter.ProvideData(tmpLoadedProjects);
                        if (tmpProjectsToImport != null)
                        {
                            if (this.AvailableProjects.Any(item => tmpProjectsToImport.Any(proj => proj.Name == item.Name)))
                            {
                                this.ErrorMessage = "Project with the same name already exists";
                            }
                            else
                            {
                                foreach (ConsoleProject tmpProject in tmpProjectsToImport)
                                {
                                    this.storageManager.SaveProject(tmpProject);
                                    this.AvailableProjects.Add(new ConsoleProjectViewModel(tmpProject));
                                }
                            }
                        }
                    }
                });
        }

        public void ExportProjects()
        {
            this.RunActionWithErrorHandling(() =>
            {
                this.Logger.LogMessage("Starting exporting projects...");

                this.projectsFilter.Mode = ProjectsFilterMode.Export;
                IEnumerable<ConsoleProject> tmpFilteredProjects = this.projectsFilter.ProvideData(this.AvailableProjects.Select(item => item.Model));
                if (tmpFilteredProjects != null)
                {
                    this.saveDestinationProvider.Filter = "*.xml|XML files";
                    string tmpExportTo = this.saveDestinationProvider.ProvideData("Export.xml");
                    if (tmpExportTo != null)
                    {
                        this.storageManager.ExtractProjects(tmpFilteredProjects, tmpExportTo);
                    }
                }
            });
        }

        public void RunInExternalConsole()
        {
            this.RunAsyncAction(() =>
            {
                this.Logger.LogMessage("Starting external consle for project: {0}", this.ActiveConsole.Name);

                Guid tmpId = Guid.NewGuid();
                string tmpName = this.fileSystemContentProvider.GetTempLocationInCurrentFolderForFile(this.configurationProvider.TempFolderName, String.Format("{0}.lnk", tmpId));
                this.ExportCurrentProjectShortcut(tmpName);

                this.Logger.LogMessage("Starting process from shortcut...");
                using (Process tmpConsoleProcess = this.processManager.StartProcess(tmpName))
                {
                }

                this.Logger.LogMessage("Removing temporary shortcut: '{0}'", tmpName);
                this.fileSystemContentProvider.DeleteFile(tmpName);

                this.Logger.LogMessage("RunInExternalConsole succeeded for project: {0}", this.ActiveConsole.Name);
            }, true, null, true).Wait(1000);
        }

        internal static string GetNameForExportingShortcut(string argName)
        {
            return String.Format("{0}.lnk", argName);
        }

        private void ExportCurrentProjectShortcut(string argFilename)
        {
            this.Logger.LogMessage("Exporting project '{0}' shortcut to '{1}'", this.ActiveConsole.Name, argFilename);

            using (IWindowsShortcut tmpShortcut = this.GetShortcutForProject(this.ActiveConsole.Project))
            {
                this.fileSystemContentProvider.ExportShortcut(tmpShortcut, argFilename);
            }
        }

        internal async Task SelectFontSettingsAsync()
        {
            FontSettings tmpSettings = null;
            this.RunActionWithErrorHandling(() =>
                {
                    tmpSettings = this.fontSettingsProvider.ProvideData(this.currentFontSettings);
                }, "Error while selecting new font settings");

            if (tmpSettings != null)
            {
                this.CurrentFontSettings = tmpSettings;
                await this.RunAsyncAction(() => this.storageManager.SaveFontSettingsAsync(this.CurrentFontSettings), true, "Saving changes ...", true);
            }
        }

        public void ConfigureProjectsList(string argSelectedProjectName = null)
        {
            this.RunActionWithErrorHandling(async () =>
                {
                    this.Logger.LogMessage("Updating projects list...");
                    ProjectsListViewModel tmpCurrentSettings = this.GetProjectListViewModelForCurrentProjects();
                    await this.ConfigureProjectsList(tmpCurrentSettings, argSelectedProjectName);
                }, "Error while updating projects settings");
        }

        private ProjectsListViewModel GetProjectListViewModelForCurrentProjects()
        {
            ProjectsListViewModel tmpCurrentSettings = new ProjectsListViewModel(
                               this.AvailableProjects.Select(item => new ProjectDetailsViewModel(
                                   item.GetCopy(),
                                   false,
                                   s => this.IsProjectNameValid(s, false),
                                   this.Dispatcher,
                                   this.Logger)),
                               this.userInteractionManager,
                               this.Dispatcher,
                               this.Logger);
            return tmpCurrentSettings;
        }

        private async Task ConfigureProjectsList(ProjectsListViewModel argViewModel, string argSelectedProjectName)
        {
            ProjectsListViewModel tmpVM = null;

            this.RunActionWithErrorHandling(() =>
            {
                this.Logger.LogMessage("Opening ProjectManager window");
                argViewModel.SelectedProject = argViewModel.Projects.Where(item => item.Project.Name == argSelectedProjectName).SingleOrDefault();
                tmpVM = this.ProjectsManager.ProvideData(argViewModel);
                this.Logger.LogMessage("Configuration window closed");
            }, "Error while updating projects settings");

            if (tmpVM != null)
            {
                await this.RunAsyncAction(() =>
                {
                    this.UpdateProjectsList(tmpVM.Projects.Select(item => item.Project));
                }, true, "Updating projects...", true);
            }
            else
            {
                this.Logger.LogMessage("ProjectManager was closed - no changes were requested");
            }
        }

        private void UpdateProjectsList(IEnumerable<ConsoleProjectViewModel> argNewProjectsSet)
        {
            this.Logger.LogMessage("ProjectManager was closed - project changes were requested.");
            IList<ConsoleProjectViewModel> tmpOriginalprojectsList = this.AvailableProjects;
            this.AvailableProjects = new ObservableCollection<ConsoleProjectViewModel>(argNewProjectsSet.OrderBy(item => item.Name));

            this.Logger.LogMessage("Removing missing projects from projects configuration");
            this.RemoveMissingProjectsFromPersistentStorage(tmpOriginalprojectsList, this.AvailableProjects);

            this.Logger.LogMessage("Adding new created projects...");
            this.AddNewCreatedProjects(tmpOriginalprojectsList, this.AvailableProjects);

            this.Logger.LogMessage("Updating projects with changes...");
            foreach (ConsoleProjectViewModel tmpUpdatedProjectDetails in this.AvailableProjects)
            {
                this.Logger.LogMessage("Persisting project details for project: {0}", tmpUpdatedProjectDetails.Name);
                this.storageManager.UpdateProject(tmpUpdatedProjectDetails.Name, tmpUpdatedProjectDetails.Model);

                foreach (ConsoleViewModel tmpProjectInstance in this.GetRunningProjectInstances(tmpUpdatedProjectDetails.Name).OfType<ConsoleViewModel>())
                {
                    this.Logger.LogMessage("Updated project instance {0}", tmpProjectInstance.Name);
                    tmpProjectInstance.Project = tmpUpdatedProjectDetails;
                }
            }
            this.Logger.LogMessage("Finished updating projects details");
        }

        private void AddNewCreatedProjects(IList<ConsoleProjectViewModel> argOriginalProjectsList, ObservableCollection<ConsoleProjectViewModel> argNewProjectsList)
        {
            foreach (ConsoleProjectViewModel tmpProject in argNewProjectsList.Where(np => !argOriginalProjectsList.Any(op => op.Name == np.Name)))
            {
                this.storageManager.SaveProject(tmpProject.Model);
            }
        }

        private void RemoveMissingProjectsFromPersistentStorage(IList<ConsoleProjectViewModel> argOriginalProjectsList, IList<ConsoleProjectViewModel> argNewProjectsList)
        {
            foreach (var tmpProjectToRemove in argOriginalProjectsList.Where(item => !argNewProjectsList.Any(ap => ap.Name == item.Name)))
            {
                this.storageManager.DeleteProject(tmpProjectToRemove.Name);
            }
        }

        internal void DuplicateCurrentProject()
        {
            this.RunActionWithErrorHandling(async () =>
                {
                    if (this.ActiveConsole == null)
                    {
                        this.Logger.LogWarning("DuplicateProject called for non-active project");
                    }
                    else
                    {
                        this.Logger.LogMessage("Duplicating project {0}", this.ActiveConsole.Project.Name);
                        ConsoleProjectViewModel tmpProjectVM = this.ActiveConsole.Project.GetCopy();
                        tmpProjectVM.Name = this.GenerateNameForDuplicateProject(tmpProjectVM.Model.Name);
                        await this.CreateProject(tmpProjectVM);
                    }
                }, "Failed to duplicate current project");
        }

        internal void OpenOutputLogForActiveProject()
        {
            this.RunActionWithErrorHandling(() =>
                {
                    ConsoleViewModel tmpConsole = this.ActiveConsole;
                    if (tmpConsole != null)
                    {
                        Process.Start(tmpConsole.OutputLogPath);
                    }
                });
        }

        internal void ImportShortcut()
        {
            this.RunActionWithErrorHandling(async () =>
                {
                    IWindowsShortcut tmpShortcut = null;
                    tmpShortcut = this.importLinkProvider.GetShortcut();
                    if (tmpShortcut != null)
                    {
                        try
                        {
                            await this.CreateProject(new ConsoleProjectViewModel(tmpShortcut));
                        }
                        finally
                        {
                            tmpShortcut.Dispose();
                        }
                    }
                }, "Failed to import project");
        }

        internal async Task CreateNewProjectAsync()
        {
            this.Logger.LogMessage("Creating new project...");
            ConsoleProjectViewModel tmpNewProject = new ConsoleProjectViewModel
                {
                    Name = NewProjectName,
                    Executable = DefaultExecutableName,
                    WorkingDir = this.fileSystemContentProvider.GetCurrentDirectory(),
                    Commands = new ObservableCollection<CommandDataViewModel>()
                };
            await this.CreateProject(tmpNewProject);
        }

        private async Task CreateProject(ConsoleProjectViewModel argVM)
        {
            ProjectsListViewModel tmpCurrentSettings = this.GetProjectListViewModelForCurrentProjects();
            ProjectDetailsViewModel tmpVM = new ProjectDetailsViewModel(argVM, true, pn => this.IsProjectNameValid(pn, true), this.Dispatcher, this.Logger);
            tmpCurrentSettings.Projects.Add(tmpVM);

            await this.ConfigureProjectsList(tmpCurrentSettings, tmpVM.Project.Name);
        }

        internal void EditActiveProject()
        {
            this.RunActionWithErrorHandling(() =>
                {
                    this.ConfigureProjectsList(this.ActiveConsole.Project.Name);
                });
        }

        private bool IsProjectNameValid(string argName, bool argIsNew)
        {
            int tmpNamedProjectsCount = this.AvailableProjects.Count(item => item.Name.Equals(argName, StringComparison.InvariantCultureIgnoreCase));
            return tmpNamedProjectsCount == (argIsNew ? 0 : 1);
        }

        private void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != "ErrorMessage" && this.ErrorMessage != null)
            {
                this.ErrorMessage = null;
            }
        }

        private void LoadGlobalCommands(IEnumerable<CommandData> argCommands)
        {
            this.RunActionWithErrorHandling(() =>
            {
                this.GlobalCommands = argCommands == null ? null : argCommands.Select(item => new CommandDataViewModel(item)).ToList();
            }, "Unable to load global commands");
        }

        private void LoadProjects()
        {
            this.RunActionWithErrorHandling(() =>
            {
                this.Logger.LogMessage("Loading projects");
                this.BusyStatus = "Loading Projects...";

                IEnumerable<ConsoleProjectViewModel> tmpProjects = this.storageManager.LoadProjects(Constants.ProjectsFilename).Select(item => new ConsoleProjectViewModel(item));
                foreach (ConsoleProjectViewModel tmpProject in tmpProjects)
                {
                    this.Logger.LogMessage("Loading project: {0}", tmpProject.Name);
                    this.AddProjectToProjectsList(tmpProject);
                }
                this.Logger.LogMessage("Successfully loaded the projects");

                this.Logger.LogMessage("Starting autostart projects...");
                Parallel.ForEach(tmpProjects.Where(item => item.AutoLoad), tmpAutoStartProject => this.StartProjectAsync(tmpAutoStartProject).Wait());
            },
            "Unable to load projects");
        }

        private void LoadPlugins()
        {
            this.RunActionWithErrorHandling(() =>
                {
                    this.Logger.LogMessage("Loading plugins");
                    this.PluginManager.Initalize();

                    IEnumerable<PluginDetails> tmpAvailablePlugins = this.PluginManager.AvailablePlugins;
                    if (tmpAvailablePlugins != null)
                    {
                        foreach (PluginDetails tmpDetails in tmpAvailablePlugins)
                        {
                            if (tmpDetails.AutoLoad)
                            {
                                this.Plugins.Add(this.PluginManager.LoadPlugin(tmpDetails.Name));
                            }
                        }
                    }
                });
        }

        private void AddProjectToProjectsList(ConsoleProjectViewModel argProject)
        {
            ObservableCollection<ConsoleProjectViewModel> tmpProjects = new ObservableCollection<ConsoleProjectViewModel>(this.AvailableProjects);
            tmpProjects.Add(argProject);
            this.AvailableProjects = new ObservableCollection<ConsoleProjectViewModel>(tmpProjects.OrderBy(item => item.Name));
        }

        public async Task StartProjectAsync(ConsoleProjectViewModel argProject)
        {
            try
            {
                this.Logger.LogMessage("Starting instance of project {0}", argProject.Name);
                string tmpName = this.GenerateNameForProjectInstance(argProject.Name);

                ILogger outputLogger;
                string outputLogFile = null;
                if (argProject.StoreOutput)
                {
                    outputLogFile = System.IO.Path.Combine(this.configurationProvider.LogOutputFolderName, String.Format("{0}.{1}.log", tmpName, DateTime.Now.ToString("yyyyMMdd_hhmmss")));
                    if (!this.fileSystemContentProvider.DirectoryExists(this.configurationProvider.LogOutputFolderName))
                    {
                        this.fileSystemContentProvider.CreateDirectory(this.configurationProvider.LogOutputFolderName);
                    }

                    outputLogger = this.outputLoggerFactory(outputLogFile);
                }
                else
                {
                    outputLogger = EmptyLogger.Instance;
                }

                ConsoleViewModel tmpVM = new ConsoleViewModel(
                    argProject,
                    this.GlobalCommands,
                    this.Dispatcher,
                    this.configurationProvider,
                    this.keyboardStateProvider,
                    this.CurrentFontSettings,
                    tmpName,
                    this.Logger,
                    outputLogger,
                    outputLogFile);
                tmpVM.Closing += OnProject_Closing;
                tmpVM.PersistChangesRequest += OnProject_PersistChangesRequest;

                await this.RunActionOnUIThread(() =>
                    {
                        lock (this.projectsLock)
                        {
                            this.Projects.Add(tmpVM);
                        }
                    });
                this.ActiveConsole = tmpVM;

                await tmpVM.StartAsync();
                this.Logger.LogMessage("Successfully started project {0}", argProject.Name);
            }
            catch (Exception ex)
            {
                string tmpError = "Unable to stat the process.";
                if (ex is UnauthorizedAccessException)
                {
                    tmpError += " You have no enough permissions for this operation.";
                }
                this.Logger.LogError(ex.ToString());
                this.ErrorMessage = tmpError;
            }
        }

        private string GenerateNameForProjectInstance(string argProjectName)
        {
            return GetUniqueNameFromSet<IConsoleVM>(argProjectName, this.Projects, item => item.Name, (name, index) => GetProjectInstanceName(name, index));
        }

        private string GenerateNameForDuplicateProject(string argProjectName)
        {
            return GetUniqueNameFromSet<ConsoleProjectViewModel>(argProjectName, this.AvailableProjects, item => item.Name, (name, index) => GetDuplicateProjectName(name, index));
        }

        internal static string GetUniqueNameFromSet<T>(string argName, IEnumerable<T> argItems, Func<T, string> argItemNameProvider, Func<string, int, string> argInstanceNameProvider)
        {
            int tmpInstanceIndex = 0;
            while (argItems.Any(item => String.Equals(argItemNameProvider(item), argInstanceNameProvider(argName, tmpInstanceIndex), StringComparison.InvariantCultureIgnoreCase)))
            {
                tmpInstanceIndex++;
            }

            return argInstanceNameProvider(argName, tmpInstanceIndex);
        }

        private static string GetProjectInstanceName(string argProjectName, int argInstanceNumber)
        {
            return String.Format(ProjectInstanceNameFormat, argProjectName, argInstanceNumber);
        }

        internal static string GetDuplicateProjectName(string argProjectName, int argDuplicateIndex)
        {
            return String.Format(DuplicateProjectNameFormat, argProjectName, argDuplicateIndex);
        }

        private void OnProject_PersistChangesRequest(IConsoleVM argSender)
        {
            ConsoleViewModel tmpSender = argSender as ConsoleViewModel;
            this.storageManager.UpdateProject(tmpSender.Project.Name, tmpSender.Project.Model);
        }

        private void OnProject_Closing(IConsoleVM argSender)
        {
            string tmpStatusMessasge = String.Format("Closing project: {0}", argSender.Name);

            this.Logger.LogMessage("Received close request from project: '{0}'", argSender.Name);
            this.RunActionSwitchingToBusyState(() =>
                {
                    this.Logger.LogMessage("Processing close request from project: '{0}'", argSender.Name);
                    if (this.ActiveConsole == argSender)
                    {
                        this.SwitchToConsole(false);
                    }

                    argSender.Closing -= OnProject_Closing;
                    argSender.PersistChangesRequest -= OnProject_PersistChangesRequest;

                    this.Logger.LogMessage("Removing project {0} from running projects list...", argSender.Name);
                    lock (this.projectsLock)
                    {
                        this.Projects.Remove(argSender);
                    }
                    this.Logger.LogMessage("Sucessfully removed project {0} from running projects list!", argSender.Name);
                }, true, tmpStatusMessasge);
        }

        private void RunCommand(CommandDataViewModel argCommand)
        {
            if (this.ActiveConsole != null)
            {
                this.ActiveConsole.ExecuteCommand(argCommand);
            }
        }

        private async Task CreateQuickConsoleAsync()
        {
            await this.StartProjectAsync(new ConsoleProjectViewModel
            {
                Name = "Quick Project",
                BackgroundColor = Colors.Black,
                MessageColor = Colors.White,
                CaretColor = Colors.White,
                ErrorMessageColor = Colors.Red,
                IsEditable = false,
                WorkingDir = AppDomain.CurrentDomain.BaseDirectory,
                ShowExplorerTreeByDefault = false
            });
        }

        private void OnActiveConsoleChanging()
        {
            if (this.ActiveConsole != null)
            {
                foreach (IPlugin tmpPlugin in this.Plugins)
                {
                    tmpPlugin.Deactivate(this.ActiveConsole);
                }
                this.ActiveConsole.ShowRecentMessagesOnly = true;
            }
        }

        private void OnActiveConsoleChanged()
        {
            if (this.ActiveConsole != null)
            {
                this.ActiveConsole.GlobalCommands = this.GlobalCommands;
            }

            this.ActivateLoadedPlugins();
        }

        private void ActivateLoadedPlugins()
        {
            foreach (IPlugin tmpPlugin in this.Plugins)
            {
                try
                {
                    tmpPlugin.Activate(this.ActiveConsole);
                }
                catch (Exception ex)
                {
                    this.Logger.LogError("Failed to activate plugin: {0}. Details: {1}", tmpPlugin.Name, ex.ToString());
                }
            }
        }

        private void OnCurrentFontSettingsChanged()
        {
            lock (this.projectsLock)
            {
                foreach (IConsoleVM tmpVM in this.Projects)
                {
                    tmpVM.CurrentFontSettings = this.CurrentFontSettings;
                }
            }
        }

        #region Properties
        public ObservableCollection<IConsoleVM> Projects
        {
            get
            {
                return this.projectsInstances;
            }
            set
            {
                if (value != this.projectsInstances)
                {
                    this.projectsInstances = value;
                    this.NotifyPropertyChange("Projects");
                }
            }
        }

        public ConsoleViewModel ActiveConsole
        {
            get
            {
                return this.activeConsole;
            }
            set
            {
                if (value != this.activeConsole)
                {
                    this.OnActiveConsoleChanging();
                    this.activeConsole = value;
                    this.OnActiveConsoleChanged();
                    this.NotifyPropertyChange("ActiveConsole");
                }
            }
        }

        public ICommand QuickConsoleCommand
        {
            get
            {
                return this.quickConsoleCommand.Value;
            }
        }

        public ICommand LoadProjectCommand
        {
            get
            {
                return this.loadProjectCommand.Value;
            }
        }

        public ICommand CustomConsoleCommand
        {
            get
            {
                return this.customConsoleCommand.Value;
            }
        }

        public ObservableCollection<ConsoleProjectViewModel> AvailableProjects
        {
            get
            {
                return this.availableProjects;
            }
            private set
            {
                if (value != this.availableProjects)
                {
                    this.availableProjects = value;
                    this.NotifyPropertyChange("AvailableProjects");
                }
            }
        }

        public IList<CommandDataViewModel> GlobalCommands
        {
            get
            {
                return this.globalCommands;
            }
            set
            {
                if (value != this.globalCommands)
                {
                    this.globalCommands = value;
                    if (this.ActiveConsole != null)
                    {
                        this.ActiveConsole.GlobalCommands = this.GlobalCommands;
                    }
                }
            }
        }

        private IPluginManager PluginManager
        {
            get
            {
                return this.pluginManager;
            }
        }

        public IList<IPlugin> Plugins
        {
            get
            {
                return this.plugins;
            }
        }

        public FontSettings CurrentFontSettings
        {
            get
            {
                return this.currentFontSettings;
            }
            set
            {
                this.currentFontSettings = value;
                this.OnCurrentFontSettingsChanged();
                this.NotifyPropertyChange("CurrentFontSettings");
            }
        }

        private bool IsAdminRole
        {
            get
            {
                if (!this.isAdminRole.HasValue)
                {
                    try
                    {
                        WindowsIdentity tmpIdentity = WindowsIdentity.GetCurrent();
                        WindowsPrincipal tmpPrincipal = new WindowsPrincipal(tmpIdentity);
                        this.isAdminRole = tmpPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
                    }
                    catch
                    {
                        this.Logger.LogError("Failed to identify administrator role membership for current user.");
                    }
                }

                return this.isAdminRole.Value;
            }
        }

        public string TitleText
        {
            get
            {
                return String.Format("ConsoleHoster Version {0}{1}", this.CurrentAssemblyVersion, this.IsAdminRole ? " (Administrator)" : String.Empty);
            }
        }

        public IFontSettingsProvider FontSettingsProvider
        {
            get
            {
                return this.fontSettingsProvider;
            }
        }

        public IGlobalCommandsProvider GlobalCommandsProvider
        {
            get
            {
                return this.globalCommandsProvider;
            }
        }

        public IProjectsManager ProjectsManager
        {
            get
            {
                return this.projectsManager;
            }
        }

        public IKeyboardInputHandler KeyboardInputHandler
        {
            get
            {
                return this.keyboardInputHandler;
            }
        }
        #endregion
    }
}